<template>
    <div :id="formId" class="dzs-form-box">
        <dzs-form-data-copy-and-paste v-if="copyAndPaste && !inline" :formItem="formItem" :formData="fromModel"
            :simple="simple" @paste="pasteData($event)"></dzs-form-data-copy-and-paste>
        <el-form v-loading="loadingInfo.loading" :element-loading-text="loadingInfo.loadingText"
            :element-loading-spinner="loadingInfo.spinner" :element-loading-background="loadingInfo.background"
            v-bind="{ ...formProps }" :model="fromModel" :rules="fromRules" :label-position="labelPosition"
            ref="dzsForm"
            :class="{ ruleForm: true, noScroll: loadingInfo.loading, 'phone-form': formBoxWidth <= 768, 'form-box-scroll': isScroll }"
            :style="{ height: isScroll ? myHeight : '100%' }">
            <el-row :gutter="gutter">
                <template v-for="(item, index) in formItem">
                    <el-col v-show="item.isShow" :span="item.span ? item.span : 24" v-if="!item.isHidden">
                        <template v-if="item.isSlot">
                            <!-- 自定义组件 -->
                            <slot :name="item.key"></slot>
                        </template>
                        <dzs-form-item v-else :item="item">
                            <template v-for="(slotProps, slotName) in $slots" #[slotName]="slotProps">
                                <slot :name="slotName" v-bind="slotProps"></slot>
                            </template>
                            <template #default>
                                <dzs-item :status="status" :inline="inline" :formBoxWidth="formBoxWidth"
                                    :toolbar="toolbar" :value="getFromModelValue(item.key)" :item="item"
                                    @change="changeVaule">
                                </dzs-item>
                            </template>
                        </dzs-form-item>
                        <template v-if="item.type == 'divider'">
                            <el-divider v-bind="{ ...item.props }">
                                <span v-if="item.props && item.props.tips">{{ item.props.tips }}</span>
                                <i v-if="item.props && item.props.icon" :class="item.props.icon"></i>
                            </el-divider>
                        </template>
                    </el-col>
                </template>
                <el-col :span="btnSpan" v-if="inline">
                    <div
                        :class="{ 'form-sbumit-box form-sbumit-search-box': true, 'form-sbumit-box-phone': btnSpan == 24 }">
                        <dzs-button v-if="showButton" class="btn" type="primary" @click.stop="onSubmit('onSearch')">
                            {{ bottonText.searchText ? bottonText.searchText : '搜索' }}
                        </dzs-button>
                        <dzs-button v-if="showButton" @click.stop="onReset">
                            {{ bottonText.resetText ? bottonText.resetText : '重置' }}
                        </dzs-button>
                        <dzs-button v-if="!openItems && spanCount > 18" @click="openItem" text type="primary">
                            展开
                            <el-icon>
                                <ArrowDown />
                            </el-icon>
                        </dzs-button>
                        <dzs-button v-if="openItems && spanCount > 18" @click="retractItem" text type="primary">
                            收起
                            <el-icon>
                                <ArrowUp />
                            </el-icon>
                        </dzs-button>
                        <slot name="button"></slot>
                    </div>
                </el-col>
            </el-row>
            <slot></slot>
        </el-form>
        <!-- 普通的提交按钮 -->
        <div :class="['form-sbumit-box', buttonFlex]" v-if="showFooter && !inline && status != 'details'">
            <dzs-button @click.stop="onCancel" v-if="showButton">
                {{ bottonText.cancelText ? bottonText.cancelText : '取消' }}
            </dzs-button>
            <slot name="footerBtn"></slot>
            <dzs-button type="primary" @click.stop="onSubmit()" v-if="showButton">
                {{ bottonText.submitText ? bottonText.submitText : '提交' }}
            </dzs-button>
        </div>
        <div :class="['form-sbumit-box', buttonFlex]" v-else-if="showFooter && !inline && status == 'details'">
            <dzs-button @click.stop="onCancel" v-if="showButton">
                {{ bottonText.closeText ? bottonText.closeText : '关闭' }}
            </dzs-button>
            <slot name="footer"></slot>
        </div>
    </div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted, computed } from "vue";
import dzsFormItem from "./components/dzs-form-item.vue";
import type { formItemInstance, formInstance, formProps } from "./@types";
import type { PropType } from "vue";
import type { FormInstance } from 'element-plus';
import { ElMessage } from 'element-plus';
import dzsItem from "./components/dzs-item.vue";
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
import tool from "@/utils";
import { simpleHash } from "./tool";
import dzsFormDataCopyAndPaste from "./components/dzs-form-data-copy-and-paste.vue";

const props = defineProps({
    options: {
        type: Object as PropType<formInstance>,
        default: () => {
            return {
                formProps: {
                    labelPosition: "left",
                    labelWidth: "80px",
                } as formProps,
                formItem: [] as formItemInstance[],
            };
        },
    },
    value: {
        type: Object,
        default: () => {
            return {};
        },
    },
    bottonText: {
        type: Object,
        default: () => {
            return {
                submitText: "提交",
                cancelText: "取消",
                resetText: "重置",
                searchText: '搜索',
                closeText: '关闭'
            };
        },
    },
    showButton: {
        type: Boolean,
        default: () => {
            return true;
        },
    },
    showFooter: {
        type: Boolean,
        default: () => {
            return true
        }
    },
    /**防抖时间*/
    antiShakeTime: {
        type: Number,
        default: () => {
            return 1000
        }
    },
    loadingOption: {
        type: Object,
        default: () => {
            return {
                background: "rgba(0,0,0,0.5)",
                text: "加载中...",
                spinner: "el-icon-loading",
                loading: false
            }
        }
    },
    /**el-row 配置*/
    gutter: {
        type: Number,
        default: () => {
            return 10
        }
    },
    /**是否开启超出滚动*/
    isScroll: {
        type: Boolean,
        default: () => {
            return true
        }
    },
    /**form表单高度*/
    height: {
        type: [String, Number],
        default: () => {
            return "100%"
        }
    },
    /**botton位置*/
    buttonFlex: {
        type: String,
        default: () => {
            return "right"
        }
    },
    /**是否为横向搜索表单*/
    inline: {
        type: Boolean,
        default: () => {
            return false
        }
    },
    /**当前状态 add/edit/details*/
    status: {
        type: String,
        default: () => {
            return 'add'
        }
    },
    /** 是否开启复制粘贴功能*/
    copyAndPaste: {
        type: Boolean,
        default: () => {
            return true
        }
    }
})

const loadingInfo = computed(() => {
    return Object.assign({
        background: "rgba(255,255,255,0.5)",
        text: "加载中...",
        spinner: "el-icon-loading",
        loading: false
    }, props.loadingOption)
})

const myHeight = computed(() => {
    return typeof props.height == 'string' ? props.height : `${props.height}px`
})

const emit = defineEmits(["update:value", "onSubmit", "onCancel", "change", 'onReset', 'onSearch'])
type emitType = "onSubmit" | "onCancel" | "change" | 'onReset' | 'onSearch'

const fromModel = ref<Record<string, any>>({});
const fromRules = ref<Record<string, any>>({});
const formItem = ref<formItemInstance[]>([]); //表单初始数据
const formProps = ref<Record<string, any>>({}); //表单配置
const formBoxWidth = ref(0); //form-box宽度
const labelPosition = ref("left"); //对其方式
const timer = ref(0); //定时器 防止重复提交
const toolbar = ref<Array<string>>([]);
const formId = ref(`dzsForm${new Date().getTime()}`);
const dzsForm = ref<FormInstance>();
const spanCount = ref(0);
const retractItems = ref(false);
const openItems = ref(false);
const btnSpan = ref(6);
const originalFromModel = ref<Record<string, any>>({});
/**表单唯一值*/
const simple = ref('');

onMounted(() => {
    listenFormBoxWidth();
})

/**
* 初始化model
* @param {Boolean} clear 是否value清空数据
* @param {Boolean} changeSpan 是否改变span
* */
const initModel = (clear: boolean = false, changeSpan: boolean = false) => {
    initFormProps(props.options.formProps);
    initFormItem(tool.deepCopy(props.options.formItem), clear, changeSpan);
}
/**
 * 初始化form配置项
 * @param {Object} [options] 表单配置项
 * */
const initFormProps = (options: Record<string, any> | undefined) => {
    if (!options) return;
    labelPosition.value = formBoxWidth.value <= 768 ? "top" : "left";
    let myLabelPosition = options['label-position'] || options['labelPosition'];
    if (myLabelPosition) labelPosition.value = myLabelPosition;
    formProps.value = options;
}
/**
 * 初始化formItem
 * @param {Array} items 展示表单配置项
 * @param {Boolean} clear 是否value清空数据
 * @param {Boolean} changeSpan 是否改变span
 * */
const initFormItem = (items: formItemInstance[], clear: boolean, changeSpan: boolean) => {
    if (changeSpan && formItem.value.length) {
        let defaultSpan = !props.inline ? 24 : 6;
        formItem.value.forEach((item, index) => {
            item.span = formBoxWidth.value <= 768 ? 24 : items[index].span || defaultSpan; //手机端默认24
        })
        if (props.inline) setItemShowspan(items)
        return;
    }
    let myFromModel: Record<string, any> = {};
    let rules: Record<string, any> = {};
    let myFormItem: formItemInstance[] = [];
    // 定义一个映射，用于根据表单项类型设置默认值  
    const defaultValuesByType = {
        'checkbox': [],
        'uploadImg': [],
        'uploadFile': [],
        'switch': false,
    };
    type keyType = "checkbox" | "uploadImg" | "uploadFile" | "switch";
    let defaultSpan = !props.inline ? 24 : 6;
    items.forEach(item => {
        item.span = formBoxWidth.value <= 768 ? 24 : item.span || defaultSpan; //手机端默认24
        item.isShow = true;
        if (!item.props) item.props = {};
        if (props.value[item.key]) item.defaultValue = props.value[item.key];
        if (defaultValuesByType.hasOwnProperty(item.type || '') && !item.defaultValue) {
            item.defaultValue = defaultValuesByType[item.type as keyType];
        }
        switch (item.type) {
            case 'select':
                if (item.props.multiple && item.defaultValue) item.defaultValue = [];
            case 'input':
                if (item.defaultValue) item.defaultValue = item.defaultValue.toString();
            case 'switch':
                item.defaultValue = item.defaultValue ? item.defaultValue : false;
            default:
                item.defaultValue = item.defaultValue ? item.defaultValue : "";
        }

        rules[item.key] = item.rules || [];
        item.props.placeholder = getPlaceholder(item, item.type);
        myFormItem.push(item);
        // 需要修改
        if (!item.key) return;
        if (clear) {
            if (JSON.stringify(originalFromModel.value) != "{}") {
                let keyList = item.key.split(".");
                let setValue = originalFromModel.value;
                for (let i = 0; i < keyList.length; i++) {
                    setValue = setValue ? setValue[keyList[i]] : null;
                }
                myFromModel[item.key] = setValue == null || setValue == undefined ? item.defaultValue : setValue;
            } else {
                myFromModel[item.key] = item.defaultValue;
            }
        } else {
            let keyList = item.key.split(".");
            let setValue = props.value;
            for (let i = 0; i < keyList.length; i++) {
                setValue = setValue ? setValue[keyList[i]] : null;
            }
            myFromModel[item.key] = item.defaultValue || setValue;
        }
    });
    fromModel.value = transformKeysToNestedObject(myFromModel);
    formItem.value = tool.deepCopy(myFormItem);
    fromRules.value = tool.deepCopy(rules);
    if (props.inline) setItemShowspan(items);
    getOriginalFromModel();
}

/**生成原始表单数据 用来进行复制粘贴 生成hash值*/
const getOriginalFromModel = () => {
    let originalFormData: Record<string, any> = {};
    for (let i = 0; i < formItem.value.length; i++) {
        let item = formItem.value[i];
        if (item.key) {
            originalFormData[item.key] = "";
        }
    }
    simple.value = simpleHash(JSON.stringify(originalFormData));
}

/**格式化值*/
const transformKeysToNestedObject = (obj: Record<string, any>) => {
    let result = {};

    // 获取对象的所有键（避免在for-in循环中检查hasOwnProperty）  
    const keys = Object.keys(obj);

    for (let i = 0; i < keys.length; i++) {
        const keyPath = keys[i].split('.');
        const value = obj[keys[i]];
        let currentNode: any = result;

        // 遍历嵌套键数组，除了最后一个键  
        for (let j = 0; j < keyPath.length - 1; j++) {
            const currentKey = keyPath[j];

            // 使用逻辑或运算符来避免额外的检查  
            currentNode = currentNode[currentKey] || (currentNode[currentKey] = {});
        }

        // 将最后一个键和值赋给当前节点  
        currentNode[keyPath[keyPath.length - 1]] = value;
    }

    return result;
}
/**获取FromModel的参数*/
const getFromModelValue = (keyPath: string) => {
    let keyList = keyPath.split('.');
    let value = fromModel.value;
    for (let i = 0; i < keyList.length; i++) {
        value = value[keyList[i]];
    }
    return value;
}
/**
 * 设置 placeholder
 * @param {Object} item 需要处理的对象
 * @param {String} [type] 什么类型  默认input 支持 input select
 * */
const getPlaceholder = (item: formItemInstance, type?: string) => {
    if (!type) type = '';
    const types = ['date', 'time', 'select', 'uploadFile', 'uploadImg'];
    let fisrtText = '请输入';
    if (types.includes(type)) {
        fisrtText = '请选择';
    }
    let placeholder = item.props ? item.props.placeholder ? item.props.placeholder : fisrtText + item.label : fisrtText + item.label
    return placeholder
}
/**改变输入的值*/
const changeVaule = (value: any, keyPath: string) => {
    let keyList = keyPath.split('.');
    let obj = fromModel.value;
    for (let i = 0; i < keyList.length - 1; i++) {
        obj = obj[keyList[i]];
    }
    obj[keyList[keyList.length - 1]] = value;
    emit("update:value", fromModel.value);
    emit("change", {
        value: value,
        key: keyPath,
    });
}
/**
 * 清空表单数据
 * @param {Function} [callback] 回调函数
 * @return {Promise} 返回一个promise
 * */
const clearForm = (callback?: Function): Promise<boolean> => {
    return new Promise((resolve) => {
        initModel(true);
        setTimeout(() => {
            dzsForm.value?.resetFields();
            callback && callback();
            resolve(true);
        }, 0)
        emit("update:value", fromModel.value);
    })
}

/**调用饿了么表单的原生方法*/
const getFormMethod = () => {
    return dzsForm;
}
/**赋值*/
const setFormData = (data: Record<string, any>) => {
    if (JSON.stringify(data) == '{}' || !data) return;
    // 避免数据未加载完毕时，数据未赋值
    setTimeout(() => {
        fromModel.value = { ...tool.deepCopy(data) };
    }, 100)
}
/**监听form-box宽度变化*/
const listenFormBoxWidth = () => {
    let phoneToolbar = ["undo redo save | importword autosave fullscreen print help"];
    let pcToolbar = ["undo redo save| formatselect | fontsizeselect | fontselect | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | lists image searchreplace | bullist numlist outdent indent |  media table insertdatetime | removeformat hr | importword autosave fullscreen print help"];
    const resizeObserver = new ResizeObserver(entries => {
        for (let entry of entries) {
            const { width } = entry.contentRect;
            if (formBoxWidth.value == width) return
            formBoxWidth.value = width;
            labelPosition.value = width <= 768 ? "top" : "left";
            toolbar.value = width <= 768 ? phoneToolbar : pcToolbar;
            initModel(false, true);
        }
    })
    const formBox = document.querySelector(`#${formId.value}`);
    if (!formBox) return;
    resizeObserver.observe(formBox);
}

type SuccessCallback<T> = (res: T) => void;
/**
 * 点击取消
 * @param {Boolean} [isSendEmit] 是否发送取消事件
 * @param {option} option 配置对象
 * @param {Function} [option.success] 成功的回调 返回初始化的值
 * @returns {Promise}
 * */
const onCancel = <T = Record<string, any>>(isSendEmit: boolean = true, option?: { success: SuccessCallback<T> }): Promise<T> => {
    clearForm();
    isSendEmit && emit("onCancel");
    return new Promise((resolve, reject) => {
        option && option.success && option.success(fromModel.value as T);
        resolve(fromModel.value as T);
    })
}

/**提交表单*/
const onSubmit = <T = Record<string, any>>(myEmit?: emitType): Promise<T> | void => {
    if (timer.value) {
        ElMessage.error(`请勿重复提交,${props.antiShakeTime / 1000} 秒后再试`);
        return
    }
    timer.value = setTimeout(() => {
        clearTimeout(timer.value);
        timer.value = 0;
    }, props.antiShakeTime);
    let sendData: Record<string, any> = {};
    return new Promise((resolve, reject) => {
        dzsForm.value?.validate((valid) => {
            if (!valid) {
                ElMessage.error("请完善表单信息");
                reject({ code: -1, msg: "请完善表单信息" });
                return
            }
            formItem.value.forEach(item => {
                let keyPath = item.key.split('.');
                let value = fromModel.value;
                for (let i = 0; i < keyPath.length; i++) {
                    value = value[keyPath[i]];
                }
                sendData[item.key] = value;
                if (item.isNull) delete sendData[item.key];
            })
            sendData = transformKeysToNestedObject(sendData);
            if (props.value) Object.assign(sendData, props.value);
            emit("onSubmit", sendData);
            emit("update:value", sendData);
            if (myEmit) emit(myEmit, sendData);
            resolve(sendData as T);
        });
    })
}

/**设置span显示*/
const setItemShowspan = (items: formItemInstance[]) => {
    let mySpanCount = 0;
    formItem.value.forEach((item, index) => {
        item.span = formBoxWidth.value <= 768 ? 24 : items[index].span || 6;
        mySpanCount += item.span || 0;
        mySpanCount > 18 ? item.isShow = false : item.isShow = true;
        openItems.value ? item.isShow = true : null;
        retractItems.value && mySpanCount > 18 ? item.isShow = false : null;
        formBoxWidth.value < 768 ? item.isShow = true : null;
    })
    formBoxWidth.value < 768 ? mySpanCount = 0 : null;
    spanCount.value = mySpanCount;
}
const retractItem = () => {
    let spanCount = 0;
    formItem.value.forEach((item) => {
        spanCount += item.span || 0;
        spanCount > 18 ? item.isShow = false : item.isShow = true;
    })
    retractItems.value = true;
    openItems.value = false;
    setTimeout(() => {
        retractItems.value = false;
    }, 200)
}

/**展开formItem*/
const openItem = () => {
    openItems.value = true;
    formItem.value.forEach((item) => {
        item.isShow = true
    })
}

/**清空表单数据*/
const onReset = () => {
    clearForm();
    emit('onReset', fromModel.value);
}

/**粘贴数据*/
const pasteData = ($event: Record<string, any>) => {
    setFormData($event);
    emit('update:value', $event);
}

watch(() => props.options, (newValue) => {
    if (!newValue || !newValue.formItem || !newValue.formItem.length) return;
    initModel()
}, {
    deep: true,
    immediate: true
})

watch(() => props.value, (newValue) => {
    if (JSON.stringify(originalFromModel.value) == '{}' && JSON.stringify(newValue) != '{}') {
        originalFromModel.value = tool.deepCopy(newValue);
    }
    if (JSON.stringify(newValue) != JSON.stringify(fromModel.value)) {
        setFormData(newValue);
    }
}, {
    deep: true,
    immediate: true
})

defineExpose({
    getFormMethod,
    setFormData,
    onSubmit,
    onCancel,
    clearForm
})
</script>
<style lang="scss" scoped>
.dzs-form-box {

    .form-sbumit-box {
        display: flex;
        flex-wrap: wrap;
        padding: 10px 0;

        &.right {
            justify-content: flex-end;
        }

        &.left {
            justify-content: flex-start;
        }

        &.center {
            justify-content: center;
        }
    }

    .form-sbumit-search-box {
        padding: 0;
        justify-content: flex-start;
    }

    .form-box-scroll {
        overflow-y: auto;
        -ms-overflow-style: none;

        /*IE10*/
        &::-webkit-scrollbar {
            display: none;
        }
    }

    .phone-form {
        min-width: 98%;
        width: 98%;
    }
}
</style>